<?php
namespace bdhert\PhpBitfield;

use bdhert\PhpBitfield\exception\BitFieldException;
use bdhert\PhpBitfield\exception\InformatsException;
use bdhert\PhpBitfield\exception\StructException;

/**
 * 位段处理
 * Class BitRigger
 * @package bdhert\PhpBitfield
 */
class BitRigger {
    /**
     * 字符串转二进制
     * @param string $bit_str
     * @return mixed|string
     */
    public static function toBinary(string $bit_str) {
        [$i, $binary] = [0, ''];
        while (isset($bit_str[$i++])) {
            $binary .= self::pack(ord($bit_str[$i - 1]), 7);
        }

        return $binary;
    }

    /**
     * 二进制转字符串
     * @param string $binary
     * @return mixed|string
     */
    public static function toString(string $binary) {
        $bin_len  = strlen($binary);
        $bin_mod  = $bin_len % 7;

        $full_len = $bin_mod ? ($bin_len - $bin_mod + 7) : $bin_len;
        $binary   = str_pad($binary, $full_len, 0);

        [$change_str, $unit_len] = ['', (int)($full_len / 7)];
        for ($i = 0; $i < $unit_len; $i++) {
            $change_str .= chr(self::pick($i * 7, 7, $binary));
        }

        return $change_str;
    }

    /**
     * 根据定义初始化组包
     * @param array $fields
     * @param int $unit_index
     * @return mixed|string
     */
    public static function build(array $fields, int $unit_index = 0) {
        if ($unit_index > 63) throw new InformatsException('片索引超越范围', 400);

        $field_total = count($fields);
        if ($field_total > 15 || $field_total !== count($fields, 2))
            throw new InformatsException('字段定义错误', 400);

        $binary  = self::pack($unit_index, 6);
        $binary .= self::pack($field_total, 4);

        $fields = array_values($fields);
        for ($i = 0; $i < 15; $i++) {
            if (($field_num = self::valueFormat($fields[$i] ?? 0)) > 31)
                throw new InformatsException('字段超越范围', 400);

            $binary .= self::pack($field_num, 5);
        }

        $binary .= self::pack(0, 10) . self::pack(0, 10);
        return self::toString($binary);
    }

    /**
     * 标准组包
     * @param int $value
     * @param int $length
     * @return string
     */
    public static function pack(int $value, int $length): string {
        return str_pad(decbin($value), $length, 0, STR_PAD_LEFT);
    }

    /**
     * 摘取二进制值
     * @param $start
     * @param $length
     * @param string $binary
     * @return int|null
     */
    public static function pick(int $start, int $length, string $binary): ?int {
        if ($length < 1 || strlen($binary) < ($start + $length - 1)) return NULL;

        return bindec(substr($binary, $start, $length));
    }

    /**
     * 添加值
     * @param string $binary
     * @param int $value
     * @param int $bit_len
     * @return string
     */
    public static function push(string &$binary, int $value, int $bit_len) {
        if (strlen($bin_value = decbin($value)) > $bit_len) throw new InformatsException('保存格式错误', 400);
        $binary .= self::pack($value, $bit_len);
        return $binary;
    }

    /**
     * 更新位字段
     * @param int $start
     * @param int $length
     * @param string $binary
     * @param int $value
     * @return bool
     */
    public static function replace(int $start, int $length, string &$binary, int $value) {
        if (strlen($bin_v = decbin($value)) > $length) throw new StructException('设置字段超出定义长度', 400);

        try {
            $bin_f  = self::pack($value, $length);
            !isset($binary[$start]) && $binary = str_pad($binary, $start + 1, 0);
            $binary = substr_replace($binary, $bin_f, $start, $length);
        } catch (\Exception $e) {
            throw new BitFieldException($e->getMessage(), 500);
        }

        return true;
    }

    /**
     * 输入值过滤
     * @param $value
     * @return int
     */
    public static function valueFormat($value): int {
        if (!ctype_digit((string)$value)) throw new InformatsException('保存值格式错误', 400);

        return (int)$value;
    }
}